/** AppL
*/

#include <Timer.h>
#include <printf.h>
#include "data.h"
#include "alarm.h"

module AppLC @safe()
{
uses {
	interface Boot;
	interface Leds;
	interface Timer<TMilli> as Timer0;
	interface Timer<TMilli> as Timer1;
	interface Timer<TMilli> as Timer2;
	interface Timer<TMilli> as Timer3;
	interface Packet;
	interface AMPacket;
	interface SplitControl as AMControl;
	interface AMSend;
	interface Receive;
	interface LowPowerListening;
	interface Packet as AlarmPacket;
	interface AMPacket as AlarmAMPacket;
	interface AMSend as AlarmSender;
	interface Receive as AlarmReceiver;
	
	interface SampleWindow;
	interface AggregatedSample;
	interface Shift;
	
	interface Read<uint16_t> as VoltageSensor;
	interface Read<uint16_t> as TemperatureSensor;
	interface Read<uint16_t> as HumiditySensor;
	interface Read<uint16_t> as ParSensor; // photosynthetically-active radiation sensor
	interface Read<uint16_t> as TsrSensor; // total solar radiation sensor
     }
}

implementation {
	
	DataEntry record;
	message_t pkt;
	message_t pkt2;
	bool radio_busy = FALSE;
	bool learning = TRUE;
	bool data_dest_aggregated = FALSE;
	uint32_t current_step = STEP_MIN;
	uint32_t last_fire2 = 0;

	/** Start radio and sensing timer. */
	event void Boot.booted() 
	{
		radio_busy = TRUE;
		call AMControl.start();
		call Timer0.startPeriodic(SENSE_INTERVAL);
		call Timer2.startOneShot(current_step);
		if (TOS_NODE_ID == DATA_DEST) {
			call Shift.set_learning(TRUE);
			call Timer1.startOneShot(LEARN_INTERVAL);
		}
	}

	/** When radio is ready, set it free for transmission. */
	event void AMControl.startDone(error_t error) {
		if (error == SUCCESS) {
			radio_busy = FALSE;
		} else {
			call AMControl.start();
		}
	}
	
	/** Radio has been stopped.
	 * Should never happen.
	 */
	event void AMControl.stopDone(error_t error) {
	}

	/** Sampling timer event.
	 * Start all measurements.
	 */
	event void Timer0.fired() {

		call SampleWindow.store(&record);
		
		record.time = call Timer0.getNow();
		call VoltageSensor.read();
		call TemperatureSensor.read();
		call HumiditySensor.read();
		call ParSensor.read();
		call TsrSensor.read();

	}
	
	/** Aggregation timer event.
	 * This timer is active only on the data_dest node.
	 * Initially it is used for the learning interval, afterwards
	 * it is the time window beneath which data is aggregated before
	 * being sent to the base station.
	 */
	event void Timer1.fired() {
	  DataEntry entry;
	  DataMessage *data_pkt = (DataMessage*) (call Packet.getPayload(&pkt, sizeof(DataMessage)));
	
	  if (learning) {
	    call Shift.set_learning(FALSE);
	    call Timer1.startPeriodic(REPORT_INTERVAL);
	    learning = FALSE;
		call Timer3.startPeriodic(STEP_MIN);
	  } else if (!radio_busy &&  call AggregatedSample.get_sample_count() > 0) {
		entry  = call AggregatedSample.get_aggregated_sample();
		data_entry2msg(&entry, data_pkt);
		data_pkt->source = TOS_NODE_ID;
		data_pkt->time = call Timer1.getNow();
		
		if (call AMSend.send(BASE_ADDR, &pkt, sizeof(DataMessage))  == SUCCESS) {
			radio_busy = TRUE;
		}
	  }
	}
	
	/** Start averaged sampling (short period). */
	event void Timer2.fired() {
		last_fire2 = call Timer2.getNow();
		data_dest_aggregated = FALSE;
		call Timer0.startPeriodic(SENSE_INTERVAL);
		call Timer2.startOneShot(current_step);
	}
	
	/** Watch light changes and adapt time step.
	 * This triggers a reading, handlind is done in table_full handler.
	 */
	event void Timer3.fired() {
		call Timer0.startPeriodic(SENSE_INTERVAL);
	}
	
	/** Update sampling time step if necessary.
	 * Decide based on \p mean whether the time step should be changed,
	 * if that is the case notify nodes.
	 */
	void update_step(DataEntry *mean) {
		uint32_t new_time;
		AlarmMessage *data_pkt2;
		
		mean = call SampleWindow.get_mean();
		// evaluate a new sampling step
		new_time = call Shift.get_new_time(TOS_NODE_ID, mean);
		printf("new time %lu, difference %ld\n", new_time, new_time-current_step);
		printfflush();
			
		if (abs(new_time-current_step) > THR_STEP && !radio_busy) {
		
			data_pkt2 = (AlarmMessage*) (call Packet.getPayload(&pkt2, sizeof(AlarmMessage)));
			if (data_pkt2 == NULL) return;
		
			data_pkt2->source = TOS_NODE_ID;
			data_pkt2->content_type = AL_CHSTEP;
			data_pkt2->time = new_time;
		
			if (call AlarmSender.send(AM_BROADCAST_ADDR, &pkt2, sizeof(AlarmMessage))  == SUCCESS) {
				radio_busy = TRUE;
				current_step = new_time;
			}
			
			// update own time step
			call Timer2.startPeriodic(current_step);
			
		}
	}
	  
	/** Sampling table is full.
	 * Every node collects measurements in its own table, which has a fixed size.
	 * When this table is full, the average of these values is sent to the
	 * data_dest node.
	 * 
	 * The data_dest node itself does not send radio packets, instead it
	 * directly updates the aggregated values.
	 */
	event void SampleWindow.table_full(DataEntry *mean) {
		
		DataMessage *data_pkt;

		// stop averaged sampling timer
		call Timer0.stop();
		
		if (TOS_NODE_ID != DATA_DEST && !radio_busy) {
			
			
			data_pkt = (DataMessage*) (call Packet.getPayload(&pkt, sizeof(DataMessage)));
			if (data_pkt == NULL) return;
			
			// fill packet
			data_pkt->source = TOS_NODE_ID;
			data_entry2msg(mean, data_pkt);
			
			if (call AMSend.send(DATA_DEST, &pkt, sizeof(DataMessage))  == SUCCESS) {
				radio_busy = TRUE;
			}
		} else {
			// on data_dest node table_full might be triggered more often,
			// so we always check wheter the time step should be changed
			// but we only aggregate data when enough time has elapsed.
			update_step(mean);
			if (!data_dest_aggregated) {
				call Shift.set(mean, TOS_NODE_ID);
				call AggregatedSample.update(mean);
				data_dest_aggregated = TRUE;
			}
		}
	}
	
	/** Data packet has been sent. */
	event void AMSend.sendDone(message_t *msg, error_t error) {
	  if (&pkt == msg) {
	    radio_busy = FALSE;
	  }
	}

	/** Receive data packets.
	 * Data packets are receved by the data_dest node.
	 * Upon receiving data, those are checked for deviation from
	 * previous (learnt) data. If they are valid, the aggregated sample
	 * is updated.
	 * If the perceived validity of the source node has changed a notification
	 * is sent to the base station.
	 */
	event message_t *Receive.receive(message_t *msg, void *payload, uint8_t length) {
		DataMessage *data_pkt;
		DataEntry entry;
		AlarmMessage *data_pkt2;
		
		bool validity_flag, validity_after;
		
		if (length == sizeof(DataMessage)) {
			
			data_pkt = (DataMessage*) payload;
			
			data_msg2entry(data_pkt, &entry);
			
			validity_flag = call Shift.is_valid(data_pkt->source);
			
			call Shift.set(&entry, data_pkt->source);
			
			validity_after = call Shift.is_valid(data_pkt->source);
			
			// if the node is valid, update samples
			if(validity_after) {
			  call AggregatedSample.update(&entry);
			}
			
			// If validity has changed, notify base
			if (validity_flag != validity_after && !radio_busy) {
			  
			  data_pkt2 = (AlarmMessage*) (call Packet.getPayload(&pkt2, sizeof(AlarmMessage)));
			  if (data_pkt2 == NULL) return msg;
			  
			  data_pkt2->source = TOS_NODE_ID;
			  data_pkt2->object_node_id = data_pkt->source;
			  data_pkt2->content_type = validity_after ? AL_RECOVER : AL_ALARM;
			  data_pkt2->time = call Timer0.getNow();
			  
			  if (call AlarmSender.send(BASE_ADDR, &pkt2, sizeof(AlarmMessage))  == SUCCESS) {
			    radio_busy = TRUE;
			  }
			}
		}
		
		return msg;
	}

	event void VoltageSensor.readDone(error_t result, uint16_t data) {
		if (result == SUCCESS) {
			record.voltage = (int)(data/4096.0*3 *1000);
		} else {
			record.voltage = -1;
		}
	}
	
	event void TemperatureSensor.readDone(error_t result, uint16_t data) {
		if (result == SUCCESS) {
			record.temperature = data;
		} else {
			record.temperature = -1;
		}
	}
	
	event void HumiditySensor.readDone(error_t result, uint16_t data) {
		if (result == SUCCESS) {
			record.humidity = data;
		} else {
			record.humidity = -1;
		}
	}
	
	event void ParSensor.readDone(error_t result, uint16_t data) {
		if (result == SUCCESS) {
			record.lightPar = data;
		} else {
			record.lightPar = -1;
		}
	}
	
	event void TsrSensor.readDone(error_t result, uint16_t data) {
		if (result == SUCCESS) {
			record.lightTsr = data;
		} else {
			record.lightTsr = -1;
		}
	}
	
	/** Alarm packet received.
	 */
	event message_t *AlarmReceiver.receive(message_t *msg, void *payload, uint8_t length) {
		AlarmMessage *a_pkt;
		uint32_t now;
		
	  printf("---------------- alarm receive\n");
	  
		if (length == sizeof(AlarmMessage)) {
			a_pkt = (AlarmMessage*) payload;
			
			// change sample interval
			if (a_pkt->content_type == AL_CHSTEP) {
				now = call Timer2.getNow();
				if (now - last_fire2 >= a_pkt->time){
					// the interval has already elapsed
					signal Timer2.fired();
					// timer will be restarted in the event handler
				} else {
					// wait for the time remaining to complete the interval
					call Timer2.startOneShot(a_pkt->time - (now - last_fire2));
				}
				current_step = a_pkt->time;
				printf("current step %lu\n", current_step);
				printfflush();
			}
		}
		
		return msg;
	}
	
	event void AlarmSender.sendDone(message_t *msg, error_t error) {
	  if (&pkt2 == msg) {
	    radio_busy = FALSE;
	  }
	}
}
